<template>
  <el-row class="vuln-analysis">
    <el-row class="input-block">
      <el-col :span="4" style="padding-right: 16px">
        <el-select v-model="platform_to_search" placeholder="请选择包管理器">
          <el-option
              v-for="item in [{value:0, label:'Maven'}, {value:1, label:'Pypi'}, {value:2, label:'Euler'}]"
              :key="item.value"
              :label="item.label"
              :value="item.value">
          </el-option>
        </el-select>
      </el-col>
      <el-col :span="4" style="padding-right: 16px">
        <el-select v-model="type_to_search" placeholder="请选择分析对象">
          <el-option
              v-for="item in [{value:0, label:'软件包'}, {value:1, label:'漏洞'}]"
              :key="item.value"
              :label="item.label"
              :value="item.value">
          </el-option>
        </el-select>
      </el-col>
      <el-col :span="9">
        <el-input v-model="input_to_analyse" :placeholder="search_hint_str" clearable></el-input>
      </el-col>
      <el-col :span="5">
        <el-select v-model="depth" placeholder="请选择漏洞影响深度">
          <el-option
              v-for="item in [{value:0, label:1}, {value:1, label:2}, {value:2, label:3}]"
              :key="item.value"
              :label="item.label"
              :value="item.value">
          </el-option>
        </el-select>
      </el-col>
      <el-col :span="2">
        <el-button type="primary" icon="el-icon-aim" @click="analyse" style="width: 102px"> 分析</el-button>
      </el-col>
    </el-row>
    <el-row class="table-block">
      <el-col :span="17">
        <el-row class="_bold _left">漏洞路径</el-row>
        <el-table
            :data="table_path_list"
            stripe border
            style="width: 100%;"
            class="table-body"
            v-loading="is_table_loading"
        >
            <el-table-column type="expand">
              <template slot-scope="props">
                <el-row v-for="(path, index) in props.row.path_list"
                        :key="index"
                        class="path-body"
                        :class="(index<props.row.path_cnt-1) ? '_margin_bottom':''">
                  <span v-for="(node, index_) in path" :key="index_">
                    <el-tag size="small" v-if="node.substring(0,3)==='CVE'" class="cve-tag _pointer" @click="to_cve(node)">
                      {{ node }}
                    </el-tag>
                    <el-tag size="small" v-else class="pkg-tag _pointer" @click="to_pkg(node)">
                      {{ node }}
                    </el-tag>
                    <i class="el-icon-right" v-if="index_!==path.length-1" style="margin: 0 4px;"></i>
                  </span>
                </el-row>
              </template>
            </el-table-column>
            <el-table-column
                :label="pkg_path_list.length!==0?'受影响包':'包含漏洞'"
                width="250%"
                prop="cve">
              <template slot-scope="scope">
                <el-tag v-show="cve_path_list.length!==0" size="small" class="cve-tag _pointer" @click="to_cve(scope.row.cve)">
                  {{ scope.row.cve }}
                </el-tag>
                <el-tag v-show="pkg_path_list.length!==0" size="small" class="pkg-tag _pointer" @click="to_pkg(scope.row.pkg)">
                  {{ scope.row.pkg }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column
                label="CVE影响类型"
                prop="is_direct"
                :filters="[{ text: '直接影响', value: true }, { text: '传递依赖影响', value: false }]"
                :filter-method="filter_type">
              <template slot-scope="scope">
                <el-row v-if="scope.row.is_direct===true">直接影响</el-row>
                <el-row v-else>传递依赖影响</el-row>
              </template>
            </el-table-column>
            <el-table-column
                label="路径数"
                prop="path_cnt"
                sortable>
            </el-table-column>
            <el-table-column
                label="操作"
                prop="path_list">
              <template slot-scope="scope">
                <el-row class="table-opt" @click.native="show_vul_path(scope.row.path_list)">查看路径</el-row>
              </template>
            </el-table-column>
        </el-table>
      </el-col>
      <el-col :span="7" class="statistic-block">
        <el-row class="_bold _left">漏洞数据</el-row>
        <el-row class="statistic-body">
          <el-empty v-if="cve_path_list.length===0 && pkg_path_list.length===0" :image-size="100" description="暂无数据"></el-empty>
          <el-row v-show="cve_path_list.length!==0 || pkg_path_list.length!==0" id="chart-vuln-source" class="statistic-chart"></el-row>
        </el-row>
      </el-col>
    </el-row>
    <el-row v-show="cve_path_list.length!==0 || pkg_path_list.length!==0" class="graph-block">
      <el-col :span="tree_span">
        <el-row class="_bold _left">漏洞树</el-row>
        <el-row v-show="platform_to_show===0" ref="viz_vul_tree" id="viz_vul_tree" class="graph-body"></el-row>
        <el-row v-show="platform_to_show===1" ref="viz_vul_tree_pypi" id="viz_vul_tree_pypi" class="graph-body"></el-row>
        <el-row v-show="platform_to_show===2" ref="viz_vul_tree_euler" id="viz_vul_tree_euler" class="graph-body"></el-row>
      </el-col>
      <el-col :span="path_span" style="padding-left: 20px">
        <el-row class="_bold _left">路径图</el-row>
        <el-row v-show="platform_to_show===0" ref="viz_vul_path" id="viz_vul_path" class="graph-body"></el-row>
        <el-row v-show="platform_to_show===1" ref="viz_vul_path_pypi" id="viz_vul_path_pypi" class="graph-body"></el-row>
        <el-row v-show="platform_to_show===2" ref="viz_vul_path_euler" id="viz_vul_path_euler" class="graph-body"></el-row>
      </el-col>
    </el-row>
  </el-row>
</template>

<script>
import axios from "axios";
import NeoVis from 'neovis.js/dist/neovis.js';
import * as echarts from "echarts";

export default {
  name: "VulnAnalysis",
  data() {
    return {
      platform_to_search: null,
      type_to_search: null,
      input_to_analyse: '',
      depth: null,
      platform_to_show: "",
      cypher: '',
      cve_path_list: [],
      pkg_path_list: [],
      path_span: 0,
      is_table_loading: false,
      // 图谱可视化配置
      labels: {
      Package: {
        caption: 'ID',  // 节点显示的文字对应内容key
        size: "1",  // 用作节点大小的属性名。默认为1
        font: { size: 12, color: '#2c3e50' },  // 字体设置
      },
      Vulnerability: {
        caption: 'name',
        size: "1",
        font: { size: 12, color: '#2c3e50' },  // 字体设置
      },
    },
      relationships: {
        depends_on_project: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
        depends_on_pkg: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
        include_vul: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
        vul_exploit: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
        affect_pkg: {
          thickness: "1",
          caption: true,
          font: { size: 12, color: '#2c3e50'},
        },
      },
    }
  },
  methods: {
    analyse: function () {
      // 缓存分析的包管理器
      this.platform_to_show = this.platform_to_search
      // 输入判断与预处理
      let input = this.input_to_analyse.replace(/(^\s*)|(\s*$)/g, "");
      if (input === "") {
        this.$message.error('分析内容不能为空')
        return
      }
      if (this.type_to_search === null) {
        this.$message.error('请选择分析对象')
        return
      }
      if (this.depth === null) {
        this.$message.error('请选择漏洞影响深度')
        return
      }
      this.pkg_path_list = this.cve_path_list = []
      // loading
      this.is_table_loading = true
      // 针对包的分析
      if (this.type_to_search === 0) {
        let pkg = input
        let platform = this.platform_to_search
        // 后端分析
        axios({
          method: "get",
          params: { pkg: pkg, platform:platform, depth: this.depth },
          url: "http://localhost:8000/search/vuln_analyse_pkg"
        }).then(res => {
          // console.log(res)
          if (res.status !== 200) {
            this.$message.error('分析包出现未知错误')
            this.is_table_loading = false
          } else {
            this.cve_path_list = res.data.data
            if (this.cve_path_list.length === 0) {
              this.$message('未查询到影响此包的漏洞信息');
            }
            else {
              // 图表更新
              let arg1 = 0, arg2 = 0
              for (let i=0; i<this.cve_path_list.length; i++) {
                let cve_path = this.cve_path_list[i]
                if (cve_path["is_direct"]===true) { arg1++ }
                else { arg2++ }
                this.update_echarts_cve(arg1, arg2)
              }
              // 图谱更新
              this.path_span = 0
            }
            this.is_table_loading = false
          }
        })
        // 图谱更新
        if (this.platform_to_show === 0) {
          this.viz_vul_tree.clearNetwork()
          const statement = "MATCH path=(m:Package{ID:'" + pkg + "'})-[:depends_on_project*0.." +
              this.depth + "]->(n:Package)-[r:include_vul]->(v:Vulnerability) RETURN path LIMIT 100"
          this.viz_vul_tree.updateWithCypher(statement)
        }
        else if (this.platform_to_show === 1) {
          this.viz_vul_tree_pypi.clearNetwork()
          const statement = "MATCH path=(m:Package{ID:'" + pkg + "'})-[:depends_on_pkg*0.." +
              this.depth + "]->(n:Package)<-[r:affect_pkg]-(v:Vulnerability) RETURN path LIMIT 100"
          this.viz_vul_tree_pypi.updateWithCypher(statement)
        }
        else if (this.platform_to_show === 2) {
          this.viz_vul_tree_euler.clearNetwork()
          const statement = "MATCH path=(m:Package{ID:'" + pkg + "'})-[:depends_on_pkg*0.." +
              this.depth + "]->(n:Package)<-[r:vul_exploit]-(v:Vulnerability) RETURN path LIMIT 100"
          this.viz_vul_tree_euler.updateWithCypher(statement)
        }
      }
      // 针对漏洞的分析
      else {
        let cve = input
        let platform = this.platform_to_search
        axios({
          method: "get",
          params: { cve: cve, platform:platform, depth: this.depth },
          url: "http://localhost:8000/search/vuln_analyse_cve"
        }).then(res => {
          console.log(res)
          if (res.status !== 200) {
            this.$message.error('分析漏洞时出现未知错误')
            this.is_table_loading = false
          } else {
            this.pkg_path_list = res.data.data
            if (this.pkg_path_list.length === 0) {
              this.$message('漏洞不存在或未收录');
            }
            else {
              // 图表更新
              let arg1 = 0, arg2 = 0
              for (let i=0; i<this.pkg_path_list.length; i++) {
                let pkg_path = this.pkg_path_list[i]
                if (pkg_path["is_direct"]===true) { arg1++ }
                else { arg2++ }
                this.update_echarts_pkg(arg1, arg2)
              }
              // 图谱更新
              this.path_span = 0
            }
            this.is_table_loading = false
          }
        })
        // 图谱更新
        if (this.platform_to_show === 0) {
          this.viz_vul_tree.clearNetwork()
          const statement = "MATCH path=(v:Vulnerability{name:'" + cve + "'})<-[r:include_vul]-(n:Package)" +
              "<-[:depends_on_project*0.." + this.depth + "]-(m:Package) RETURN path LIMIT 100"
          this.viz_vul_tree.updateWithCypher(statement)
        }
        else if (this.platform_to_show === 1) {
          this.viz_vul_tree_pypi.clearNetwork()
          const statement = "MATCH path=(m:Package)-[:depends_on_pkg*0.." + this.depth + "]->(n:Package)" +
              "<-[r:affect_pkg]-(v:Vulnerability{CVE_ID:'" + cve + "'}) RETURN path LIMIT 100"
          this.viz_vul_tree_pypi.updateWithCypher(statement)
        }
        else if (this.platform_to_show === 2) {
          this.viz_vul_tree_euler.clearNetwork()
          const statement = "MATCH path=(m:Package)-[:depends_on_pkg*0.." + this.depth + "]->(n:Package)" +
              "<-[r:vul_exploit]-(v:Vulnerability{CVE_ID:'" + cve + "'}) RETURN path LIMIT 100"
          this.viz_vul_tree_euler.updateWithCypher(statement)
        }
      }
    },
    to_cve: function (cve) {
      window.open("https://cve.mitre.org/cgi-bin/cvename.cgi?name=" + cve)
    },
    to_pkg: function (pkg) {
      let platform = "maven"
      if (this.platform_to_show === 1) {
        platform = "pypi"
      }
      else if (this.platform_to_show === 2) {
        platform = "euler"
      }
      this.$emit("change_tag", { to: 2, pkg: pkg, platform: platform})
    },
    show_vul_path(path_list) {
      let pkg = path_list[0][0]
      let cve = path_list[0][path_list[0].length-1]
      // 不同平台在不同数据库执行查询
      if (this.platform_to_show === 0) {
        this.viz_vul_path.clearNetwork()
        const statement = "MATCH path=(m:Package{ID:'" + pkg + "'})-[:depends_on_project*0.." +
            this.depth + "]->(n:Package)-[r:include_vul]->(v:Vulnerability{name:'" + cve + "'}) RETURN path"
        this.viz_vul_path.updateWithCypher(statement)
      }
      else if (this.platform_to_show === 1) {
        this.viz_vul_path_pypi.clearNetwork()
        const statement = "MATCH path=(m:Package{ID:'" + pkg + "'})-[:depends_on_pkg*0.." +
            this.depth + "]->(n:Package)<-[r:affect_pkg]-(v:Vulnerability{CVE_ID:'" + cve + "'}) RETURN path"
        this.viz_vul_path_pypi.updateWithCypher(statement)
      }
      else if (this.platform_to_show === 2) {
        this.viz_vul_path_euler.clearNetwork()
        const statement = "MATCH path=(m:Package{ID:'" + pkg + "'})-[:depends_on_pkg*0.." +
            this.depth + "]->(n:Package)<-[r:vul_exploit]-(v:Vulnerability{CVE_ID:'" + cve + "'}) RETURN path"
        this.viz_vul_path_euler.updateWithCypher(statement)
      }
      // span动态变化
      if (path_list.length > 20) {
        this.path_span = 17
      }
      else if (path_list.length < 3) {
        this.path_span = 7
      }
      else {
        const span = 7 + (17 - 7) / (20 - 3) * (path_list.length - 3)
        this.path_span = Math.round(span)
      }
    },
    // 表格filter
    filter_type(value, row) {
      return row.is_direct === value;
    },
    // 图谱配置方法
    init_graph_maven() {
      let config_1 = {
        container_id: 'viz_vul_tree', // dom元素id
        server_url: 'bolt://211.71.15.39:8687', // 注意不为7474
        server_user: 'neo4j',
        server_password: 'maven',
        // 节点样式配置
        labels: this.labels,
        // 关系样式配置
        relationships: this.relationships,
        // 其他配置
        arrows: true,  // 关系线段是否显示箭头
        hierarchical: true, // 节点显示方式(是否启用分层布局)
        hierarchical_sort_method: 'directed',
        encrypted: 'ENCRYPTION_OFF',
        trust: 'TRUST_ALL_CERTIFICATES',
        initial_cypher: "",
      }
      let config_2 = {
        container_id: 'viz_vul_path', // dom元素id
        server_url: 'bolt://211.71.15.39:8687', // 注意不为7474
        server_user: 'neo4j',
        server_password: 'maven',
        // 节点样式配置
        labels: this.labels,
        // 关系样式配置
        relationships: this.relationships,
        // 其他配置
        arrows: true,  // 关系线段是否显示箭头
        hierarchical: true, // 节点显示方式(是否启用分层布局)
        hierarchical_sort_method: 'directed',
        encrypted: 'ENCRYPTION_OFF',
        trust: 'TRUST_ALL_CERTIFICATES',
        initial_cypher: "",
      }
      this.viz_vul_tree = new NeoVis(config_1)
      this.viz_vul_tree.render()
      this.viz_vul_path = new NeoVis(config_2)
      this.viz_vul_path.render()
    },
    init_graph_pypi() {
      let config_1 = {
        container_id: 'viz_vul_tree_pypi', // dom元素id
        server_url: 'bolt://211.71.15.39:9687', // 注意不为7474
        server_user: 'neo4j',
        server_password: 'python',
        // 节点样式配置
        labels: this.labels,
        // 关系样式配置
        relationships: this.relationships,
        // 其他配置
        arrows: true,  // 关系线段是否显示箭头
        hierarchical: true, // 节点显示方式(是否启用分层布局)
        hierarchical_sort_method: 'directed',
        encrypted: 'ENCRYPTION_OFF',
        trust: 'TRUST_ALL_CERTIFICATES',
        initial_cypher: "",
      }
      let config_2 = {
        container_id: 'viz_vul_path_pypi', // dom元素id
        server_url: 'bolt://211.71.15.39:9687', // 注意不为7474
        server_user: 'neo4j',
        server_password: 'python',
        // 节点样式配置
        labels: this.labels,
        // 关系样式配置
        relationships: this.relationships,
        // 其他配置
        arrows: true,  // 关系线段是否显示箭头
        hierarchical: true, // 节点显示方式(是否启用分层布局)
        hierarchical_sort_method: 'directed',
        encrypted: 'ENCRYPTION_OFF',
        trust: 'TRUST_ALL_CERTIFICATES',
        initial_cypher: "",
      }
      this.viz_vul_tree_pypi = new NeoVis(config_1)
      this.viz_vul_tree_pypi.render()
      this.viz_vul_path_pypi = new NeoVis(config_2)
      this.viz_vul_path_pypi.render()
    },
    init_graph_euler() {
      let config_1 = {
        container_id: 'viz_vul_tree_euler', // dom元素id
        server_url: 'bolt://211.71.15.39:7687', // 注意不为7474
        server_user: 'neo4j',
        server_password: 'euler',
        // 节点样式配置
        labels: this.labels,
        // 关系样式配置
        relationships: this.relationships,
        // 其他配置
        arrows: true,  // 关系线段是否显示箭头
        hierarchical: true, // 节点显示方式(是否启用分层布局)
        hierarchical_sort_method: 'directed',
        encrypted: 'ENCRYPTION_OFF',
        trust: 'TRUST_ALL_CERTIFICATES',
        initial_cypher: "",
      }
      let config_2 = {
        container_id: 'viz_vul_path_euler', // dom元素id
        server_url: 'bolt://211.71.15.39:7687', // 注意不为7474
        server_user: 'neo4j',
        server_password: 'euler',
        // 节点样式配置
        labels: this.labels,
        // 关系样式配置
        relationships: this.relationships,
        // 其他配置
        arrows: true,  // 关系线段是否显示箭头
        hierarchical: true, // 节点显示方式(是否启用分层布局)
        hierarchical_sort_method: 'directed',
        encrypted: 'ENCRYPTION_OFF',
        trust: 'TRUST_ALL_CERTIFICATES',
        initial_cypher: "",
      }
      this.viz_vul_tree_euler = new NeoVis(config_1)
      this.viz_vul_tree_euler.render()
      this.viz_vul_path_euler = new NeoVis(config_2)
      this.viz_vul_path_euler.render()
    },
    // 图表配置方法
    init_echarts() {
      this.myChart = echarts.init(document.getElementById("chart-vuln-source"));
    },
    update_echarts_cve(arg1, arg2) {
      const option = {
        legend: {
          // 图例
          data: ["直接漏洞", "间接漏洞"],
          right: "23.5%",
          top: "90%",
          orient: "horizontal"
        },
        title: {
          text: "漏洞来源",
          textStyle: {fontSize: 15, color:"#2c3e50"},
          y: "41%",
          x: "center"
        },
        series: [
          {
            top: "0%",
            type: "pie",
            center: ['50%', '46%'],
            label: {
              show: true,
              formatter: "{c}" // b代表名称，c代表对应值，d代表百分比
            },
            radius: ["40%", "70%"], //圆环内半径40%，外半径70%
            data: [
              {
                value: arg1,
                name: "直接漏洞"
              },
              {
                value: arg2,
                name: "间接漏洞"
              }
            ]
          }
        ]
      };
      this.myChart.setOption(option);
    },
    update_echarts_pkg(arg1, arg2) {
      const option = {
        legend: {
          // 图例
          data: ["直接影响包数", "间接影响包数"],
          right: "17%",
          top: "90%",
          orient: "horizontal"
        },
        title: {
          text: "影响类型",
          textStyle: {fontSize: 15, color:"#2c3e50"},
          y: "41%",
          x: "center"
        },
        series: [
          {
            top: "0%",
            type: "pie",
            center: ['50%', '46%'],
            label: {
              show: true,
              formatter: "{c}" // b代表名称，c代表对应值，d代表百分比
            },
            radius: ["40%", "70%"], //圆环内半径40%，外半径70%
            data: [
              {
                value: arg1,
                name: "直接影响包数"
              },
              {
                value: arg2,
                name: "间接影响包数"
              }
            ]
          }
        ]
      };
      this.myChart.setOption(option);
    }
  },
  computed: {
    tree_span() {
      return 24 - this.path_span
    },
    search_hint_str() {
      if (this.platform_to_search === 0) {
        if (this.type_to_search === 0) {
          return "请输入Maven包坐标 ( [groupId]:[artifactId]@[version] )"
        }
        else if (this.type_to_search === 1) {
          return "请输入准确的CVE-ID"
        }
        else {
          return ""
        }
      }
      else if (this.platform_to_search === 1) {
        if (this.type_to_search === 0) {
          return "请输入Pypi包ID"
        }
        else if (this.type_to_search === 1) {
          return "请输入准确的CVE-ID"
        }
        else {
          return ""
        }
      }
      else if (this.platform_to_search === 2) {
        if (this.type_to_search === 0) {
          return "请输入Euler包ID( [name]+[version] )"
        }
        else if (this.type_to_search === 1) {
          return "请输入准确的CVE-ID"
        }
        else {
          return ""
        }
      }
      else {
        return ""
      }
    },
    table_path_list() {
      if (this.cve_path_list.length !== 0) {
        return this.cve_path_list.slice(0, 100)
      }
      else {
        return this.pkg_path_list.slice(0, 100)
      }
    }
  },
  mounted() {
    // 图谱配置
    this.init_graph_maven()
    this.init_graph_pypi()
    this.init_graph_euler()
    // 图表配置
    this.init_echarts()
  },
}
</script>

<style scoped>
.input-block {
  margin-top: 20px;
}

.table-block {
  margin-top: 20px;
}
.table-body {
  margin-top: 20px;
  min-height: 220px;
}
.table-opt {
  color: #0e90ff;
}
.table-opt:hover {
  color: #1ea4ff;
  cursor: pointer;
}

.path-body {
  border-left: #7e8e9f 4px solid;
  border-radius: 4px;
  margin: 0 20px;
  padding: 5px 15px 7px 15px;
  line-height: 32px;
  transition: .3s;
}
.path-body:hover {
  background-color: rgba(44, 62, 80, 0.05);
}

.statistic-block {
  padding-left: 20px;
}
.statistic-body {
  margin-top: 20px;
}
.statistic-chart {
  width: 330px;
  height: 200px;
}

.graph-block {
  margin-top: 20px;
  margin-bottom: 20px;
}
.graph-body {
  margin-top: 20px;
  width: 100%;
  height: 480px;
  border-left: #7e8e9f 4px solid;
  border-right: #f2f3f6 1px solid;
  border-radius: 4px;
  background-color: rgba(44, 62, 80, 0.05);
}

.cve-tag {
  background-color: #fff2f5;
  border-color: #ffdbe2;
  color: crimson;
}
.pkg-tag {
  background-color: #fdf6ec;
  border-color: #faecd8;
  color: #e6a33e;
}

._bold {
  font-weight: bold;
}
._left {
  text-align: left;
}
._margin_bottom {
  margin-bottom: 16px;
}
._pointer:hover {
  cursor: pointer;
}
._transition {
  transition: .5s;
}


.el-button--primary {
  background-color: #0e90ff;
  font-size: 15px;
}
.el-button--primary:hover {
  background-color: #1ea4ff;
}
.table-body >>> .cell {
  font-size: 15px;
}
.table-body >>> .el-table__column-filter-trigger {
  margin-left: 5px;
}
.table-body >>> i {
  font-size: 16px;
}
.table-body >>> .el-table th.el-table__cell {
  padding: 6px 0;
}
.table-body >>> .el-table__expand-icon {
  margin-top: -6px;
}

.table-block >>> .el-table__body-wrapper {
  max-height: 340px;
  overflow-y: auto;
}
.table-block >>> .el-table__body-wrapper::-webkit-scrollbar{
  width: 6px;
}
.table-block >>> .el-table__body-wrapper::-webkit-scrollbar-thumb{
  border-radius: 3px;
  min-height: 30px;
  background: rgba(144,147,153,.3);
}
</style>